{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "GA-Satellite Optimization.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "authorship_tag": "ABX9TyOMFbptz2FNlTjSJnz/BhRy",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/dsskonuru/ga-satellite-constellation-optimization/blob/master/GA_Satellite_Optimization.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "JnFcCgU3bqIg",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "N = 12  # of satellites\n",
        "k = 4  # of subconstellations\n",
        "subconstellation_size = N // k  # satellites per subconstellation"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-kgOeH_lV_iB",
        "colab_type": "text"
      },
      "source": [
        ">$\\text{Ising:} \\qquad  E(\\bf{s}|\\bf{h},\\bf{J})\n",
        "= \\left\\{ \\sum_{i=1}^N h_i s_i + \\sum_{i<j}^N J_{i,j} s_i s_j  \\right\\}$\n",
        "\n",
        ">$\\small E \\quad {\\longrightarrow} \\quad Objective \\; function \\; (to \\; be \\; minimized) \n",
        "\\\\ \\small s_i \\quad {\\longrightarrow} \\quad Constellation \n",
        "\\\\ \\small h_i \\quad {\\longrightarrow} \\quad Coverage \\; of \\; the \\; constellation\n",
        "\\\\ \\small J_{i,j} \\quad {\\longrightarrow} \\quad Penality \\; for \\; pairs \\; having \\; a \\; satellite \\; in \\; common$"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "WyJQnYPTc3Hp",
        "colab_type": "code",
        "outputId": "2c0206bb-229a-4dbc-876f-7fe7c8e790f1",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        }
      },
      "source": [
        "import numpy as np\n",
        "\n",
        "coverage = {}\n",
        "for i in range(N):\n",
        "  coverage[i] = np.random.randint(0, 100)/100\n",
        "print(coverage)\n",
        "\n",
        "score_threshold = 0.4"
      ],
      "execution_count": 24,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "{0: 0.23, 1: 0.73, 2: 0.38, 3: 0.68, 4: 0.36, 5: 0.62, 6: 0.08, 7: 0.99, 8: 0.82, 9: 0.1, 10: 0.16, 11: 0.0}\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "bd_kJmVmrMms",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "outputId": "f42d1c77-80ff-46b6-e0d4-2cc3a7c326a3"
      },
      "source": [
        "import itertools\n",
        "\n",
        "subconstellations = {} # create dict of subconstellations and its coverage\n",
        "\n",
        "for subconstellation in itertools.combinations(range(N), N//k):\n",
        "    score = sum(coverage[v] for v in subconstellation) / subconstellation_size\n",
        "\n",
        "    if score < score_threshold:\n",
        "        continue\n",
        "\n",
        "    subconstellations[frozenset(subconstellation)] = score\n",
        "\n",
        "print(subconstellations)    \n",
        "print(len(subconstellations))"
      ],
      "execution_count": 25,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "{frozenset({0, 1, 2}): 0.4466666666666666, frozenset({0, 1, 3}): 0.5466666666666667, frozenset({0, 1, 4}): 0.43999999999999995, frozenset({0, 1, 5}): 0.5266666666666667, frozenset({0, 1, 7}): 0.65, frozenset({0, 1, 8}): 0.5933333333333333, frozenset({0, 2, 3}): 0.43, frozenset({0, 2, 5}): 0.41, frozenset({0, 2, 7}): 0.5333333333333333, frozenset({0, 8, 2}): 0.4766666666666666, frozenset({0, 3, 4}): 0.42333333333333334, frozenset({0, 3, 5}): 0.51, frozenset({0, 3, 7}): 0.6333333333333333, frozenset({0, 8, 3}): 0.5766666666666667, frozenset({0, 4, 5}): 0.4033333333333333, frozenset({0, 4, 7}): 0.5266666666666667, frozenset({0, 8, 4}): 0.47, frozenset({0, 5, 7}): 0.6133333333333333, frozenset({0, 8, 5}): 0.5566666666666666, frozenset({0, 6, 7}): 0.43333333333333335, frozenset({0, 8, 7}): 0.68, frozenset({0, 9, 7}): 0.44, frozenset({0, 10, 7}): 0.45999999999999996, frozenset({0, 11, 7}): 0.4066666666666667, frozenset({0, 8, 10}): 0.4033333333333333, frozenset({1, 2, 3}): 0.5966666666666667, frozenset({1, 2, 4}): 0.48999999999999994, frozenset({1, 2, 5}): 0.5766666666666667, frozenset({1, 2, 7}): 0.6999999999999998, frozenset({8, 1, 2}): 0.6433333333333332, frozenset({1, 2, 9}): 0.4033333333333333, frozenset({1, 2, 10}): 0.4233333333333333, frozenset({1, 3, 4}): 0.59, frozenset({1, 3, 5}): 0.6766666666666667, frozenset({1, 3, 6}): 0.49666666666666676, frozenset({1, 3, 7}): 0.8000000000000002, frozenset({8, 1, 3}): 0.7433333333333333, frozenset({1, 3, 9}): 0.5033333333333334, frozenset({1, 10, 3}): 0.5233333333333333, frozenset({11, 1, 3}): 0.47000000000000003, frozenset({1, 4, 5}): 0.57, frozenset({1, 4, 7}): 0.6933333333333334, frozenset({8, 1, 4}): 0.6366666666666666, frozenset({1, 10, 4}): 0.4166666666666666, frozenset({1, 5, 6}): 0.47666666666666674, frozenset({1, 5, 7}): 0.7799999999999999, frozenset({8, 1, 5}): 0.7233333333333333, frozenset({1, 5, 9}): 0.4833333333333334, frozenset({1, 10, 5}): 0.5033333333333333, frozenset({1, 11, 5}): 0.45, frozenset({1, 6, 7}): 0.6, frozenset({8, 1, 6}): 0.5433333333333333, frozenset({8, 1, 7}): 0.8466666666666667, frozenset({1, 9, 7}): 0.6066666666666667, frozenset({1, 10, 7}): 0.6266666666666666, frozenset({1, 11, 7}): 0.5733333333333334, frozenset({8, 1, 9}): 0.5499999999999999, frozenset({8, 1, 10}): 0.57, frozenset({8, 1, 11}): 0.5166666666666666, frozenset({2, 3, 4}): 0.47333333333333333, frozenset({2, 3, 5}): 0.56, frozenset({2, 3, 7}): 0.6833333333333332, frozenset({8, 2, 3}): 0.6266666666666666, frozenset({10, 2, 3}): 0.4066666666666667, frozenset({2, 4, 5}): 0.4533333333333333, frozenset({2, 4, 7}): 0.5766666666666667, frozenset({8, 2, 4}): 0.52, frozenset({2, 5, 7}): 0.6633333333333333, frozenset({8, 2, 5}): 0.6066666666666666, frozenset({2, 6, 7}): 0.48333333333333334, frozenset({8, 2, 6}): 0.4266666666666667, frozenset({8, 2, 7}): 0.73, frozenset({9, 2, 7}): 0.49000000000000005, frozenset({2, 10, 7}): 0.51, frozenset({2, 11, 7}): 0.4566666666666667, frozenset({8, 9, 2}): 0.43333333333333335, frozenset({8, 2, 10}): 0.4533333333333333, frozenset({3, 4, 5}): 0.5533333333333333, frozenset({3, 4, 7}): 0.6766666666666667, frozenset({8, 3, 4}): 0.62, frozenset({3, 5, 6}): 0.46, frozenset({3, 5, 7}): 0.7633333333333333, frozenset({8, 3, 5}): 0.7066666666666667, frozenset({9, 3, 5}): 0.46666666666666673, frozenset({10, 3, 5}): 0.48666666666666664, frozenset({11, 3, 5}): 0.43333333333333335, frozenset({3, 6, 7}): 0.5833333333333334, frozenset({8, 3, 6}): 0.5266666666666667, frozenset({8, 3, 7}): 0.83, frozenset({9, 3, 7}): 0.59, frozenset({10, 3, 7}): 0.61, frozenset({11, 3, 7}): 0.5566666666666666, frozenset({8, 9, 3}): 0.5333333333333333, frozenset({8, 10, 3}): 0.5533333333333333, frozenset({8, 11, 3}): 0.5, frozenset({4, 5, 7}): 0.6566666666666666, frozenset({8, 4, 5}): 0.6, frozenset({4, 6, 7}): 0.4766666666666666, frozenset({8, 4, 6}): 0.42, frozenset({8, 4, 7}): 0.7233333333333333, frozenset({9, 4, 7}): 0.4833333333333334, frozenset({10, 4, 7}): 0.5033333333333333, frozenset({11, 4, 7}): 0.45, frozenset({8, 9, 4}): 0.4266666666666667, frozenset({8, 10, 4}): 0.4466666666666666, frozenset({5, 6, 7}): 0.5633333333333334, frozenset({8, 5, 6}): 0.5066666666666667, frozenset({8, 5, 7}): 0.8099999999999999, frozenset({9, 5, 7}): 0.57, frozenset({10, 5, 7}): 0.59, frozenset({11, 5, 7}): 0.5366666666666666, frozenset({8, 9, 5}): 0.5133333333333333, frozenset({8, 10, 5}): 0.5333333333333333, frozenset({8, 11, 5}): 0.48, frozenset({8, 6, 7}): 0.63, frozenset({10, 6, 7}): 0.41, frozenset({8, 9, 7}): 0.6366666666666667, frozenset({8, 10, 7}): 0.6566666666666666, frozenset({8, 11, 7}): 0.6033333333333334, frozenset({9, 10, 7}): 0.4166666666666667}\n",
            "120\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "uTVK9M_JDJjQ",
        "colab": {}
      },
      "source": [
        "def init_pop(sol_per_pop):\n",
        "  population = []\n",
        "  for i in range(sol_per_pop):\n",
        "    constellation = [np.random.choice(list(subconstellations.keys())) for j in range(k)]\n",
        "    population.append(constellation)\n",
        "  return population\n"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "42_46MUeDogW",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "outputId": "a0e79c9c-7c98-4c71-af57-29c2bb73fc42"
      },
      "source": [
        "sol_per_pop = 10\n",
        "\n",
        "new_population = init_pop(sol_per_pop)\n",
        "\n",
        "print(new_population)\n",
        "print(len(new_population))"
      ],
      "execution_count": 27,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[[frozenset({8, 2, 5}), frozenset({2, 4, 5}), frozenset({1, 2, 4}), frozenset({11, 5, 7})], [frozenset({8, 2, 6}), frozenset({8, 1, 3}), frozenset({0, 1, 5}), frozenset({8, 11, 7})], [frozenset({8, 3, 4}), frozenset({8, 1, 10}), frozenset({11, 5, 7}), frozenset({8, 1, 5})], [frozenset({8, 4, 6}), frozenset({9, 4, 7}), frozenset({1, 4, 7}), frozenset({8, 1, 11})], [frozenset({0, 8, 2}), frozenset({8, 1, 4}), frozenset({0, 3, 5}), frozenset({2, 3, 4})], [frozenset({0, 3, 7}), frozenset({10, 3, 7}), frozenset({3, 5, 6}), frozenset({0, 1, 4})], [frozenset({3, 5, 7}), frozenset({3, 4, 7}), frozenset({9, 5, 7}), frozenset({10, 5, 7})], [frozenset({8, 10, 4}), frozenset({0, 8, 4}), frozenset({0, 3, 7}), frozenset({0, 1, 2})], [frozenset({10, 6, 7}), frozenset({8, 1, 6}), frozenset({2, 10, 7}), frozenset({0, 8, 2})], [frozenset({8, 1, 10}), frozenset({11, 4, 7}), frozenset({0, 3, 7}), frozenset({1, 5, 7})]]\n",
            "10\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Ku23lgn-vc9z",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def cal_fitness(population):\n",
        "  fitness = []\n",
        "\n",
        "  for con in population:\n",
        "    coverage = sum([subconstellations[subcon] for subcon in con])\n",
        "\n",
        "    # penalty multiple subcons sharing the same satellite\n",
        "    for a, b in itertools.combinations(con, 2): \n",
        "      if not a.isdisjoint(b):\n",
        "        coverage-=1\n",
        "\n",
        "    fitness.append(coverage)\n",
        "      \n",
        "  return fitness"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0ndiZvU-fGQR",
        "colab_type": "code",
        "outputId": "83d9ec6a-fb49-4825-b8fb-e4668838ca74",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        }
      },
      "source": [
        "fitness = cal_fitness(new_population)\n",
        "print(fitness)"
      ],
      "execution_count": 29,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[-2.9133333333333336, -1.6999999999999997, -1.5500000000000003, -2.8866666666666667, -2.9033333333333333, -1.856666666666667, -3.4000000000000004, -2.003333333333334, -2.06, -1.5666666666666669]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "J9pCZF5DMLkr",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def select_mating_pool(population, fitness, num_parents):\n",
        "    # Selecting the best individuals in the current generation as parents for producing the offspring of the next generation.\n",
        "    parents = []\n",
        "    for parent_num in range(num_parents):\n",
        "        max_fitness_idx = fitness.index(max(fitness))\n",
        "        parents.append(population[max_fitness_idx])\n",
        "        fitness[max_fitness_idx] = -999\n",
        "    return parents"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "m6AfELsOO76O",
        "colab_type": "code",
        "outputId": "288f3f11-5ceb-4177-9f54-510d098ea7ad",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        }
      },
      "source": [
        "num_parents_mating = sol_per_pop // 2\n",
        "parents = select_mating_pool(new_population, fitness, num_parents_mating)\n",
        "\n",
        "print(parents)\n",
        "len(parents)"
      ],
      "execution_count": 31,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[[frozenset({8, 3, 4}), frozenset({8, 1, 10}), frozenset({11, 5, 7}), frozenset({8, 1, 5})], [frozenset({8, 1, 10}), frozenset({11, 4, 7}), frozenset({0, 3, 7}), frozenset({1, 5, 7})], [frozenset({8, 2, 6}), frozenset({8, 1, 3}), frozenset({0, 1, 5}), frozenset({8, 11, 7})], [frozenset({0, 3, 7}), frozenset({10, 3, 7}), frozenset({3, 5, 6}), frozenset({0, 1, 4})], [frozenset({8, 10, 4}), frozenset({0, 8, 4}), frozenset({0, 3, 7}), frozenset({0, 1, 2})]]\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "5"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 31
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TWmSvix-MSXU",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def crossover(parents, offspring_size):\n",
        "    offsprings = []\n",
        "    # The point at which crossover takes place between two parents. Usually it is at the center.\n",
        "    crossover_point = np.random.randint(k)\n",
        "\n",
        "    for i in range(offspring_size):\n",
        "        # Index of the first parent to mate.\n",
        "        parent1_idx = i%len(parents)\n",
        "        # Index of the second parent to mate.\n",
        "        parent2_idx = (i+1)%len(parents)\n",
        "        offspring = parents[parent1_idx][:crossover_point]+parents[parent2_idx][crossover_point:]\n",
        "        offsprings.append(offspring)\n",
        "\n",
        "    return offsprings"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "r8D4s6ZUPcFa",
        "colab_type": "code",
        "outputId": "8f160167-5cfc-4ca8-e0f0-05ffb17220d9",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        }
      },
      "source": [
        "offspring_crossover = crossover(parents, offspring_size=sol_per_pop-len(parents))\n",
        "\n",
        "print(offspring_crossover)\n",
        "len(offspring_crossover)"
      ],
      "execution_count": 33,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[[frozenset({8, 3, 4}), frozenset({8, 1, 10}), frozenset({0, 3, 7}), frozenset({1, 5, 7})], [frozenset({8, 1, 10}), frozenset({11, 4, 7}), frozenset({0, 1, 5}), frozenset({8, 11, 7})], [frozenset({8, 2, 6}), frozenset({8, 1, 3}), frozenset({3, 5, 6}), frozenset({0, 1, 4})], [frozenset({0, 3, 7}), frozenset({10, 3, 7}), frozenset({0, 3, 7}), frozenset({0, 1, 2})], [frozenset({8, 10, 4}), frozenset({0, 8, 4}), frozenset({11, 5, 7}), frozenset({8, 1, 5})]]\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "5"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 33
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "je1HIDKgMrlt",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def mutation(offspring_crossover):\n",
        "    # Mutation changes a single gene in each offspring randomly.\n",
        "    for idx in range(len(offspring_crossover)):\n",
        "        # The random value to be added to the gene.\n",
        "        random_idx = np.random.randint(N*k)\n",
        "        random_value = np.random.choice(list(subconstellations.keys()))\n",
        "        offspring_crossover[idx] = offspring_crossover[idx][:random_idx] + [random_value] + offspring_crossover[idx][random_idx+1:]\n",
        "    return offspring_crossover"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "QXZO6WEPQEPg",
        "colab_type": "code",
        "outputId": "8cb3eeb7-188f-4891-9cf9-bf90d8976315",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        }
      },
      "source": [
        "offspring_mutation = mutation(offspring_crossover)\n",
        "\n",
        "print(offspring_mutation)\n",
        "print(len(offspring_mutation))"
      ],
      "execution_count": 35,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[[frozenset({8, 3, 4}), frozenset({8, 1, 10}), frozenset({0, 3, 7}), frozenset({1, 5, 7}), frozenset({2, 3, 5})], [frozenset({8, 1, 10}), frozenset({11, 4, 7}), frozenset({0, 1, 5}), frozenset({8, 11, 7}), frozenset({8, 10, 3})], [frozenset({8, 2, 6}), frozenset({8, 1, 3}), frozenset({3, 5, 6}), frozenset({0, 1, 4}), frozenset({8, 1, 4})], [frozenset({0, 3, 7}), frozenset({10, 3, 7}), frozenset({0, 3, 7}), frozenset({0, 1, 2}), frozenset({0, 3, 4})], [frozenset({8, 10, 4}), frozenset({0, 8, 4}), frozenset({11, 5, 7}), frozenset({8, 1, 5}), frozenset({2, 4, 7})]]\n",
            "5\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6XDGLLitRSB4",
        "colab_type": "code",
        "outputId": "7f052353-7bdf-4aba-9bf5-42f8e142d61f",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        }
      },
      "source": [
        "new_population = parents + offspring_mutation\n",
        "\n",
        "print(new_population)\n",
        "len(new_population)"
      ],
      "execution_count": 36,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[[frozenset({8, 3, 4}), frozenset({8, 1, 10}), frozenset({11, 5, 7}), frozenset({8, 1, 5})], [frozenset({8, 1, 10}), frozenset({11, 4, 7}), frozenset({0, 3, 7}), frozenset({1, 5, 7})], [frozenset({8, 2, 6}), frozenset({8, 1, 3}), frozenset({0, 1, 5}), frozenset({8, 11, 7})], [frozenset({0, 3, 7}), frozenset({10, 3, 7}), frozenset({3, 5, 6}), frozenset({0, 1, 4})], [frozenset({8, 10, 4}), frozenset({0, 8, 4}), frozenset({0, 3, 7}), frozenset({0, 1, 2})], [frozenset({8, 3, 4}), frozenset({8, 1, 10}), frozenset({0, 3, 7}), frozenset({1, 5, 7}), frozenset({2, 3, 5})], [frozenset({8, 1, 10}), frozenset({11, 4, 7}), frozenset({0, 1, 5}), frozenset({8, 11, 7}), frozenset({8, 10, 3})], [frozenset({8, 2, 6}), frozenset({8, 1, 3}), frozenset({3, 5, 6}), frozenset({0, 1, 4}), frozenset({8, 1, 4})], [frozenset({0, 3, 7}), frozenset({10, 3, 7}), frozenset({0, 3, 7}), frozenset({0, 1, 2}), frozenset({0, 3, 4})], [frozenset({8, 10, 4}), frozenset({0, 8, 4}), frozenset({11, 5, 7}), frozenset({8, 1, 5}), frozenset({2, 4, 7})]]\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "10"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 36
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Q34xGqaOMVft",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "sol_per_pop = 25\n",
        "num_generations = 25000\n",
        "\n",
        "fitness_g = []\n",
        "new_population = init_pop(sol_per_pop)\n",
        "\n",
        "for generation in range(num_generations):\n",
        "    \n",
        "    # Measuring the fitness of each chromosome in the population.\n",
        "    fitness = cal_fitness(new_population)\n",
        "\n",
        "    # Selecting the best parents in the population for mating.\n",
        "    parents = select_mating_pool(new_population, fitness, num_parents_mating)\n",
        "\n",
        "    # Generating next generation using crossover.\n",
        "    offspring_crossover = crossover(parents, offspring_size=sol_per_pop-len(parents))\n",
        "\n",
        "    # Adding some variations to the offsrping using mutation.\n",
        "    offspring_mutation = mutation(offspring_crossover)\n",
        "\n",
        "    # Creating the new population based on the parents and offspring.\n",
        "    new_population = parents + offspring_mutation\n",
        "\n",
        "    fitness_g.append(max(fitness))\n",
        "\n",
        "# Getting the best solution after iterating finishing all generations.\n",
        "fitness = cal_fitness(new_population)\n",
        "# Then return the index of that solution corresponding to the best fitness.\n",
        "best_match_idx = np.where(fitness == np.max(fitness))[0][0]"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "DYMyiDPQGETo",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 51
        },
        "outputId": "142c0311-a706-48f5-c7bf-be2aed1062c6"
      },
      "source": [
        "print(max(fitness))\n",
        "print(new_population[best_match_idx])"
      ],
      "execution_count": 38,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "1.2466666666666661\n",
            "[frozenset({1, 3, 4}), frozenset({0, 8, 5}), frozenset({1, 3, 4}), frozenset({2, 10, 7})]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "HnFmNeZPt3K2",
        "colab_type": "code",
        "outputId": "21691551-1efa-4432-cb51-877f2e5bb70a",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 282
        }
      },
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(fitness_g)"
      ],
      "execution_count": 39,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[<matplotlib.lines.Line2D at 0x7f982b7bf0f0>]"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 39
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    }
  ]
}